2-4. 네트워크 계열
관통 질문: 개발 편의로 켠 소켓 옵션이 패킷 탈취 통로가 되지 않았는가?
17. 같은 포트번호의 다중 연결 CWE-605
신뢰 경계:
SO_REUSEPORT+INADDR_ANY바인딩 → UID 검증 없이 → 같은 포트를 공유하는 다른 프로세스 ⚠️ SO_REUSEADDR 보다 SO_REUSEPORT 가 실질적 위험이다. 같은 UID 로 도는 코드는 그 포트의 연결을 나눠 받을 수 있다.
공격 시나리오: 리눅스 TCP는 커널이 막지만(listen 중이면 bind 실패) UDP는 이 제한이 없다. SO_REUSEPORT 는 같은 주소·포트에 여러 소켓을 정식으로 허용하고 커널이 연결을 분배하므로, 같은 UID로 도는 다른 코드가 연결의 일부를 조용히 받아간다. 정상 서버가 계속 동작하기 때문에 탈취가 드러나지 않는다.
❌ Bad
int fd = socket(AF_INET, SOCK_STREAM, 0);
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof on);
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* 모든 인터페이스 */
bind(fd, (struct sockaddr *)&addr, sizeof addr);
listen(fd, backlog);
/* 같은 UID 로 도는 다른 코드가 이 그룹에 낄 수 있다 */✅ Good
inet_pton(AF_INET, "10.0.0.7", &addr.sin_addr); /* 서비스할 인터페이스로 범위 축소 */
#ifdef USE_PORT_SHARDING
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof on); /* 부하 분산이 필요할 때만 */
#endif
bind(fd, (struct sockaddr *)&addr, sizeof addr);
listen(fd, backlog);
/* 켰다면 이 포트의 UID 가 곧 신뢰 경계 — 전용 계정으로 실행 */치환 규칙: INADDR_ANY 대신 실제 서비스 인터페이스로 바인딩 범위 축소, SO_REUSEPORT 는 부하 분산이 실제 필요할 때만 켜고 전용 계정으로 격리. UDP 서버는 리눅스에서도 탈취가 성립하므로 수신 데이터 인증·무결성 검증을 별도로 둔다.
보충 — OS별 포트 가로채기 가능 여부 (2012년 원문 대비 현재 커널 기준)
구분 TCP 가로채기 UDP 가로채기 리눅스 막힌다(listen 중이면 bind 실패) 가능하다 — 이 제한이 없다 FreeBSD·macOS 가능하다(같은 사용자 또는 root) 가능하다 윈도우 SO_EXCLUSIVEADDRUSE로 차단 가능가능하다
SO_REUSEPORT는 취약점이 아니라 멀티 프로세스 서버의 정식 부하분산 기능이다. 다만 유일한 보호 장치가 “그룹의 모든 소켓은 실효 UID가 같아야 한다”는 것뿐이므로, 이 포트의 신뢰 경계는 방화벽이 아니라 UID다.점검 체크리스트
- 서비스 계정을 전용으로 분리했는가 — 그 계정으로 다른 코드가 돌고 있지 않은가
- 배포 스크립트·cron·운영 도구가 같은 계정을 쓰고 있지 않은가
- 컨테이너라면 네트워크 네임스페이스를 공유하는 사이드카의 UID를 확인했는가
SO_REUSEPORT를 켠 포트와 그 이유를 문서로 남겼는가- 점검 명령:
ss -lntp 'sport = :8080'— 예상 워커 수보다 많은 프로세스가 보이면 즉시 조사
이 계열 미니 체크리스트
-
SO_REUSEADDR/SO_REUSEPORT를 습관적으로(에러 회피용으로) 켜고 있지 않은가 -
INADDR_ANY로 바인딩할 필요가 실제로 있는가, 특정 인터페이스로 좁힐 수 있는가 -
SO_REUSEPORT를 쓰는 서비스 계정이 다른 프로세스와 공유되지 않는가 - UDP 서버라면 수신 데이터의 인증·무결성 검증이 별도로 있는가